package com.aikg.kgrag_java.service.impl;


import cn.hutool.core.bean.BeanUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
import com.aikg.kgrag_java.common.Result;
import com.aikg.kgrag_java.pojo.entity.FileStorage;
import com.aikg.kgrag_java.pojo.vo.FileVo;
import com.aikg.kgrag_java.service.FileStorageService;
import com.aikg.kgrag_java.utils.MinioUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.aikg.kgrag_java.mapper.FileStorageMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @description 针对表【file_storage(文件存储表，用于存储文件的基本信息，如文件名、类型、URL和MD5哈希值，以及文件所属的知识库名称)】的数据库操作Service实现
* @createDate 2025-01-03 09:30:26
*/
@Service
public class FileStorageServiceImpl extends ServiceImpl<FileStorageMapper, FileStorage>
    implements FileStorageService {

    // 引入 Minio 工具类
    @Resource
    private MinioUtil minioUtil;

    @Resource
    private FileStorageMapper fileStorageMapper;

    @Resource
    private ElasticsearchClient elasticsearchClient;

    /**
     * 上传文件
     * @param uploadFile 上传的文件
     * @param projectId 项目的ID
     * @param knowledgeBase 知识库名称
     * @param request HttpServletRequest 请求
     * @return Result 返回操作结果
     */
    @Override
    public Result uploadFile(MultipartFile uploadFile, Integer projectId, String knowledgeBase, HttpServletRequest request) {

        if(uploadFile == null || projectId == null || knowledgeBase == null){
            return Result.buildErrorResult("参数不能为空");
        }

        try {
            // 校验文件是否为空
            if (uploadFile.isEmpty()) {
                return Result.buildErrorResult("文件不能为空");
            }

            // 计算文件的MD5哈希值，用于判断文件是否已经上传
            String md5Hash = DigestUtils.md5Hex(uploadFile.getInputStream());

            // 检查文件是否已经存在，如果存在则返回已上传
            FileStorage existingFile = fileStorageMapper.selectOne(new QueryWrapper<FileStorage>()
                    .eq("md5_hash", md5Hash)
                    .eq("knowledge_db_name", knowledgeBase)
                    .eq("project_id",projectId));

            if (existingFile != null) {
                return Result.buildErrorResult("文件已经存在");
            }

//            //获取文件后缀
//            String fileExtension = uploadFile.getOriginalFilename().substring(uploadFile.getOriginalFilename().indexOf("."));

            // 获取文件的完整名称
            String fileName = uploadFile.getOriginalFilename();

            // 获取最后一个 . 字符的位置
            int dotIndex = fileName.lastIndexOf(".");
            String fileExtension = "";

            if (dotIndex != -1 && dotIndex < fileName.length() - 1) {
                // 从最后一个 . 后面开始获取文件后缀
                fileExtension = fileName.substring(dotIndex + 1);
            } else {
                // 如果没有 . 或 . 在文件名的最后一位
                fileExtension = "";
            }

            // 上传文件到MinIO
            minioUtil.upload(uploadFile, knowledgeBase + "/" + md5Hash + "." + fileExtension, String.valueOf(projectId));// 请替换为实际桶名

            // 存储文件信息到数据库
            FileStorage fileStorage = new FileStorage();
            fileStorage.setFileName(fileName);
            fileStorage.setFileType(fileExtension);
            fileStorage.setFileUrl(knowledgeBase + "/" + md5Hash + fileExtension);  // 根据MinIO的URL生成完整路径
            fileStorage.setMd5Hash(md5Hash);
            fileStorage.setKnowledgeDbName(knowledgeBase);
            fileStorage.setProjectId(projectId);

            // 插入文件记录到数据库
            boolean saveResult = this.save(fileStorage);
            if (saveResult) {
                return Result.buildSuccessResultWithData(new String[]{fileStorage.getFileName()});
            } else {
                return Result.buildErrorResult("文件上传失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return Result.buildErrorResult("文件上传过程中发生错误");
        }
    }


    /**
     * 删除文件
     * @param fileName
     * @param domain
     * @param knowledgeBase
     * @param request
     * @return
     */
    @Override
    public Result deleteFile(String fileName, String domain, String knowledgeBase, HttpServletRequest request) {
        return null;
    }

    /**
     *获取每个knowledgedb下文件数量
     * @param projectId
     * @return
     */
    @Override
    public Result getKnowledgeDBsFileNumber(Integer projectId) {
        // 使用 QueryWrapper 和 selectMaps() 直接在数据库中获取每个知识库下的文件数量
        QueryWrapper<FileStorage> fileStorageQueryWrapper = new QueryWrapper<>();
        fileStorageQueryWrapper.select("knowledge_db_name", "COUNT(*) AS file_count")  // 选择字段并计算文件数
                .eq("project_id", projectId)  // 根据 projectId 进行筛选
                .groupBy("knowledge_db_name");  // 按照 knowledge_db_name 分组

        // 执行查询，获取每个知识库下的文件数量
        List<Map<String, Object>> resultList = this.listMaps(fileStorageQueryWrapper);

        // 如果没有查询到任何数据，返回错误提示
        if (resultList.isEmpty()) {
            return Result.buildErrorResult("该项目下没有文件");
        }

        // 构建最终的文件数量统计结果
        Map<String, Integer> knowledgeDBFileCountMap = new HashMap<>();

        for (Map<String, Object> result : resultList) {
            String knowledgeDBName = (String) result.get("knowledge_db_name");  // 获取知识库名称
            Integer fileCount = ((Long) result.get("file_count")).intValue();  // 获取文件数量并转换为 Integer
            knowledgeDBFileCountMap.put(knowledgeDBName, fileCount);  // 存入 map
        }

        return Result.buildSuccessResultWithData(knowledgeDBFileCountMap);
    }

    /**
     * 删除知识库的某个文件，删除向量库
     * @param projectId
     * @param knowledgeDB
     * @param fileName
     * @return
     */
    @Override
    public boolean deleteKnowledgeDBFile(int projectId, String knowledgeDB, String fileName) {
        // 删除mysql中知识库的文件
        boolean knowledge_db_name = this.remove(new QueryWrapper<FileStorage>()
                .eq("knowledge_db_name", knowledgeDB)
                .eq("file_name",fileName)
                .eq("project_id",projectId));

        if (!knowledge_db_name) {
            log.error("mysql中知识库的文件删除失败");
            return false;
        }

        // 构建DeleteByQueryRequest,删除指定索引库里面的文档数据
        DeleteByQueryRequest request = new DeleteByQueryRequest.Builder()
                .index(String.valueOf(projectId))  // 索引名称
                .query(Query.of(q -> q
                        .term(t -> t
                                .field("fileName")  // 查询字段
                                .value(fileName) // 查询值
                        )
                ))
                .build();

//         执行删除操作
        try {
            DeleteByQueryResponse response = elasticsearchClient.deleteByQuery(request);
            if (response.deleted() > 0) {
                return true;
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException("es文档删除失败");
        }
        // TODO  常规操作不删除文件数据库minio里的文件

        return true;
    }

    /**
     * 获取指定知识库下所有文档名称
     *
     * @param projectId
     * @param knowledgeDB
     * @return
     */
    @Override
    public List<FileVo> getFileByProjectIdAndKnowledgeDB(int projectId, String knowledgeDB) {
        List<FileStorage> list = this.list(new QueryWrapper<FileStorage>()
                .eq("project_id", projectId)
                .eq("knowledge_db_name", knowledgeDB));

        List<FileVo> fileVos = new ArrayList<>();
        for (FileStorage file : list) {
            FileVo fileVo = new FileVo();
            BeanUtil.copyProperties(file, fileVo);
            boolean fileExist = minioUtil.isFileExist(String.valueOf(projectId), file.getFileUrl());
            fileVo.setExist(fileExist);
            fileVos.add(fileVo);
        }

        return fileVos;
    }
}




